﻿using Devil;
using GameToolkit.BehaviourTree;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Experimental.GraphView;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.UIElements;

namespace GameToolkit.Editor
{

    // 可编辑的行为树资源节点
    internal interface INodeWithComposite
    {
        Vector2 position { get; }
        bool IsDecoratorEditable { get; }
        BehaviourTreeLib.CompositeInfo compositeInfo { get; }
        Composite composite { get; set; }

        void Validate();
        int temporaryPortId { get; set; }
        int GetInputPortId(Edge edge);
        Port GetInputPort(int portId);
        int GetOutputPortId(Edge edge);
        Port GetOutputPort(int portId);
        void SaveCompositeConnections(MinHeap<INodeWithComposite> nodeBuffer);
        void LoadCompositeConnections(BehaviourTreeGraph.GraphEdgeLoader edgeLoader);
        void AutoConnectOnCreate(BehaviourTreeGraph graph, Port port);
        bool IsCompatiblePort(Port port);
    }


    //using TreeNode = BehaviourTreeGraph.TreeGraphNode;

    public class BehaviourNodeInspector : Blackboard, IDisposable
    {
        INodeWithComposite mNode;

        SerializedObject mSerializeObj;
        //SerializedProperty m_Interruption;
        //SerializedProperty m_ConditionLogic;
        SerializedProperty m_Node;
        SerializedProperty m_Decorators;
        ReorderableList mDecoratorLst;
        Button mAddBtn;
        internal int TargetCount => mNodes.Count;

        List<INodeWithComposite> mNodes = new List<INodeWithComposite>();

        public BehaviourNodeInspector(BehaviourTreeGraph graph) : base(graph)
        {
            name = "inspector";
            layer = 1;
            title = "Blackboard";
            subTitle = "inspector";
            capabilities &= ~Capabilities.Snappable;
            scrollable = true;
            style.minWidth = 280;
            style.minHeight = 250;
            style.width = 400;

            mAddBtn = this.Q<Button>("addButton");
            if (mAddBtn != null)
                mAddBtn.text = "↩";

            addItemRequested = ChangeComposite;

            var container = new IMGUIContainer(OnIMGUI);
            container.AddToClassList("inspector-imgui");
            Add(container);

            var scroll = this.Q<ScrollView>();

            if (scroll != null)
                scroll.mode = ScrollViewMode.Vertical;

        }

        private void ChangeComposite(Blackboard blackboard)
        {
            if (mNodes.Count == 0 || !mNodes[0].IsDecoratorEditable)
                return;
            var graph = graphView as BehaviourTreeGraph;
            var win = graph.AttachedWindow as BehaviourTreeEditor;
            var rect = blackboard.contentRect;
            var pos = blackboard.ChangeCoordinatesTo(win.rootVisualElement, new Vector2(rect.xMax - 130, rect.yMin + 80)) + win.position.position;
            graph.mCurrentDecorationNodes.Clear();
            var providor = win.NodeProvidor;
            providor.Filter = (item) =>
            {
                var data = item.userData as BehaviourTreeLib.CompositeInfo;
                if (data == null || mNode == null)
                    return false;
                var info = mNode.compositeInfo;
                if (info.IsExecutableNode)
                    return data.IsExecutableNode && info.IsSubTreeRoot == data.IsSubTreeRoot;
                else
                    return !data.IsExecutableNode && info.HasInputPort == data.HasInputPort;
                //return data != null && data.IsSubTreeRoot == nodeType;
            };
            providor.OnSelectEntryAction = SelectAndChangeComposite;
            SearchWindow.Open(new SearchWindowContext(pos, 260), providor);
            //foreach (var item in mNodes)
            //{
            //    graph.mCurrentDecorationNodes.Add(item);
            //}
            //if (graph.mCurrentDecorationNodes.Count > 0)
            //{
            //    SearchWindow.Open(new SearchWindowContext(pos), win.NodeProvidor);
            //}
        }

        private bool SelectAndChangeComposite(BehaviourTreeSearchProvidor providor, SearchTreeEntry entry, SearchWindowContext context)
        {
            providor.OnSelectEntryAction = null;
            providor.Filter = null;
            if (mNodes.Count == 0 || !mNodes[0].IsDecoratorEditable)
                return false;
            var comType = entry.userData as BehaviourTreeLib.CompositeInfo;
            var data = GetComposite();
            if (comType == null || comType.WrapType == data.WrapType)
                return true;
            var num = 0;
            for (int i = 0; i < mNodes.Count; i++)
            {
                var composite = comType.WrapType.Create() as Composite;
                if (composite != null)
                {
                    composite._guid = mNodes[i].composite?._guid;
                    if (string.IsNullOrEmpty(composite._guid))
                        composite._guid = GUID.Generate().ToString();
                    mNodes[i].composite = composite;
                    num++;
                }
            }
            if (num > 0)
            {
                GetSelections(true);
                var win = graphView as BehaviourTreeGraph;
                if (win != null)
                    win.IsDirty = true;
            }
            return true;
        }

        internal void GetSelections(bool forceReload = false)
        {
            if (mSerializeObj == null || mSerializeObj.targetObject == null)
            {
                if (mSerializeObj != null)
                    mSerializeObj.Dispose();
                var inst = ScriptableObject.CreateInstance<BehaviourInspectorData>();
                inst.name = "Node Inspector";
                mSerializeObj = new SerializedObject(inst);
                //m_Interruption = mSerializeObj.FindProperty("m_Interruption");
                //m_ConditionLogic = mSerializeObj.FindProperty("m_ConditionLogic");
                m_Node = mSerializeObj.FindProperty("m_Node");
                m_Decorators = mSerializeObj.FindProperty("m_Decorators");
                mDecoratorLst = new ReorderableList(mSerializeObj, m_Decorators);
                mDecoratorLst.onAddCallback = AddDecorator;
                mDecoratorLst.drawHeaderCallback = DrawDecoratorsHead;
                mDecoratorLst.elementHeightCallback = CalculateDecoratorHeight;
                mDecoratorLst.drawElementCallback = DrawDecorator;
            }
            mNodes.Clear();
            foreach (var node in graphView.selection)
            {
                var it = node as INodeWithComposite;
                if (it != null)
                    mNodes.Add(it);
            }
            var current = mNodes.Count == 0 ? null : mNodes[0];
            if (mNodes.Count == 0)
            {
                subTitle = "nothing selected";
            }
            else if (mNodes.Count > 1)
            {
                subTitle = $"{mNodes.Count} behaviour tree nodes";
            }
            if (mNode != current || forceReload)
            {
                mNode = current;
                mDecoratorLst.displayAdd = mNode != null && mNode.IsDecoratorEditable;
                mDecoratorLst.displayRemove = mDecoratorLst.displayAdd;
                if (current != null)
                {
                    var serInst = (BehaviourInspectorData)mSerializeObj.targetObject;
                    serInst.SetTarget(mNode.composite);
                    var composite = serInst.m_Node == null ? null : BehaviourTreeLib.Instance.GetCompositeInfo(serInst.m_Node.GetType());
                    if (composite == null)
                    {
                        title = "Node Inspector";
                        subTitle = "no behaviour tree node";
                    }
                    else
                    {
                        title = $"{composite.Title} ({composite.WrapType.WrapType.Name})";
                        subTitle = $"GUID: {serInst.m_Node._guid}";
                    }
                }
                else
                {
                    title = "Blackboard";
                }
                MarkDirtyRepaint();
            }
        }

        private float CalculateDecoratorHeight(int index)
        {
            var data = mSerializeObj.targetObject as BehaviourInspectorData;
            Composite inst = data.m_Decorators[index];
            float h = 23;
            if (inst is not IWithoutInspectorComposite)
            {
                var item = m_Decorators.GetArrayElementAtIndex(index);
                if (item.isExpanded)
                {
                    var lv = item.depth;
                    if (item.NextVisible(true) && item.depth > lv)
                    {
                        do
                        {
                            h += EditorGUI.GetPropertyHeight(item, true) + 2;
                        } while (item.NextVisible(false) && item.depth > lv);
                    }
                }
            }
            return h;
        }

        private void DrawDecorator(Rect rect, int index, bool isActive, bool isFocused)
        {
            var data = mSerializeObj.targetObject as BehaviourInspectorData;
            Composite inst = data.m_Decorators[index];
            var item = m_Decorators.GetArrayElementAtIndex(index);
            var row = new Rect(rect.x, rect.y, rect.width, 20);
            //var inst = data.m_Decorators[index];
            //var desc = BehaviourTreeLib.Instance.GetTitle(inst, true);
            var desc = inst is IWithoutInspectorComposite || item.isExpanded ? BehaviourTreeLib.Instance.GetTitle(inst, true) : BehaviourTreeLib.Instance.GetDescription(inst);
            var i = desc.IndexOf('\n');
            if (i > 0)
                desc = ParallelUtils.Concat(desc.Substring(0, i), "...");
            if (inst is IWithoutInspectorComposite)
                EditorGUI.LabelField(row, desc);
            else
                item.isExpanded = EditorGUI.Foldout(row, item.isExpanded, desc);
            if(inst is not IWithoutInspectorComposite && item.isExpanded)
            {
                EditorGUI.indentLevel++;
                var lv = item.depth;
                if (item.NextVisible(true) && item.depth > lv)
                {
                    do
                    {
                        var h = EditorGUI.GetPropertyHeight(item, true);
                        row.y += row.height + 2;
                        row.height = h;
                        EditorGUI.PropertyField(row, item, true);
                    } while (item.NextVisible(false) && item.depth > lv);
                }
                EditorGUI.indentLevel--;
            }
        }

        private void AddDecorator(ReorderableList list)
        {
            if (mNodes.Count == 0 || !mNodes[0].IsDecoratorEditable)
                return;
            var graph = graphView as BehaviourTreeGraph;
            var win = graph.AttachedWindow as BehaviourTreeEditor;
            var rect = contentRect;
            var pos = Event.current.mousePosition;
            pos.y += 100;
            pos.x = contentRect.xMax - 130;
            pos = this.ChangeCoordinatesTo(win.rootVisualElement, pos) + win.position.position;
            //blackboard.ChangeCoordinatesTo(win.rootVisualElement, new Vector2(rect.xMax, rect.yMin)) + win.position.position;
            graph.mCurrentDecorationNodes.Clear();
            foreach (var item in mNodes)
            {
                if (item is ExecutableNodeVisual tnode)
                    graph.mCurrentDecorationNodes.Add(tnode);
            }
            if (graph.mCurrentDecorationNodes.Count > 0)
                SearchWindow.Open(new SearchWindowContext(pos, 260), win.DecoratorProvidor);
        }

        BehaviourTreeLib.CompositeInfo GetComposite()
        {
            return mNode == null ? null : mNode.compositeInfo;
        }

        private void DrawDecoratorsHead(Rect rect)
        {
            EditorGUI.LabelField(rect, "Decorators & Conditions");
        }

        void KeyValueGUI(string varName, object value)
        {
            KeyValueGUI(varName, value, 8);
        }

        void KeyValueGUI(string varName, object value, int dropDownNum)
        {
            ICollection dropDownLst = null;
            EditorGUILayout.BeginHorizontal();
            EditorGUILayout.PrefixLabel(varName);
            if (value == null)
            {
                EditorGUILayout.LabelField("null");
            }
            else if (value.GetType().IsValueType)
            {
                EditorGUILayout.TextField(value.ToString());
            }
            else if (value is UnityEngine.Object uobj)
            {
                EditorGUILayout.ObjectField(uobj, uobj.GetType(), true);
            }
            else if (value is ICollection col)
            {
                dropDownLst = col;
                EditorGUILayout.TextField($"<Collection> (size: {col.Count})");
            }
            else
            {
                EditorGUILayout.TextField($"<{value.GetType().FullName}>");
            }
            EditorGUILayout.EndHorizontal();
            if (dropDownLst != null && dropDownNum > 0)
            {
                EditorGUI.indentLevel++;
                var num = 0;
                foreach (var v in dropDownLst)
                {
                    if (num > dropDownNum)
                    {
                        EditorGUILayout.LabelField("...");
                        break;
                    }
                    KeyValueGUI($"[{num}]", v, 0);
                    num++;
                }
                EditorGUI.indentLevel--;
            }
        }

        private void OnIMGUI()
        {
            if (mSerializeObj == null || mSerializeObj.targetObject == null)
                return;
            if (mNodes.Count > 1)
            {
                EditorGUILayout.HelpBox("当前选中了多个目标，您不能编辑属性，但可以批量给这些目标添加装饰节点。", MessageType.Info);
            }
            else if (mNodes.Count == 0)
            {
                var nothing = true;
                var graph = graphView as BehaviourTreeGraph;
                var player = graph == null ? null : graph.BindingPlayer;
                var exec = player == null ? null : player.Executable;
                if(exec != null)
                {
                    nothing = false;
                    exec.VisitAllValues(KeyValueGUI);
                }
                if (nothing)
                    EditorGUILayout.LabelField("Nothing target selected.");
            }
            else
            {

                mSerializeObj.Update();
                var inst = mSerializeObj.targetObject as BehaviourInspectorData;

                EditorGUI.BeginChangeCheck();

                //EditorGUILayout.PropertyField(m_Interruption);
                //EditorGUILayout.PropertyField(m_ConditionLogic);
                var assertInst = mNode is ExecutableNodeVisual node ? node.RuntimeInstance : null;
                if (assertInst != null)
                {
                    assertInst.IsAssert = EditorGUILayout.Toggle("Assert", assertInst.IsAssert);
                }
                if (mNode.composite is ExecutableComposite execNode)
                {
                    var p = m_Node.Copy();
                    var lv = p.depth;
                    if (p.NextVisible(true) && p.depth > lv)
                    {
                        do
                        {
                            EditorGUILayout.PropertyField(p, true);
                        } while (p.NextVisible(false) && p.depth > lv);
                    }
                }
                mDecoratorLst.DoLayoutList();

                var dirty = EditorGUI.EndChangeCheck();
                if (dirty)
                {
                    mSerializeObj.ApplyModifiedProperties();
                    var btContext = mNode.composite;
                    inst.ApplyModifyValues(ref btContext);
                    mNode.composite = btContext;
                    mNode.Validate();

                    var graph = graphView as BehaviourTreeGraph;
                    if (graph != null)
                        graph.IsDirty = true;
                }

            }
        }

        public void Dispose()
        {
            var obj = mSerializeObj == null ? null : mSerializeObj.targetObject;
            if (obj != null)
            {
                mSerializeObj.Dispose();
                GlobalUtil.Destroy(obj);
            }
            mSerializeObj = null;
        }

        public override void ClearSelection()
        {
            //base.ClearSelection();
        }

        public override void AddToSelection(ISelectable selectable)
        {
            //base.AddToSelection(selectable);
        }

        public override void RemoveFromSelection(ISelectable selectable)
        {
            //base.RemoveFromSelection(selectable);
        }
    }
}
